{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 二进制码\n",
    "\n",
    "在计算机的世界中，一切都是使用二进制的数字进行表示的，约定所讨论到的二进制数字都是存储在 「机器字长」 为 8 位（即 1 Byte）的计算机中。也就是说一个存储单元最多只能够存储 8 个二进制数字，多出来的部分都将会被抛弃掉，只保留低 8 位的数据。\n",
    "\n",
    "```{note}\n",
    "- **机器数** 是数在计算机中的二进制表示形式。机器数是带符号的，在计算机用机器数的最高位存放符号，正数为 $0$，负数为 $1$。\n",
    "- 机器数的 **真值** 是将带符号位的机器数转换为真正数值。\n",
    "```\n",
    "\n",
    "而且从硬件的角度上看，只有正数加负数才算减法，正数与正数相加，负数与负数相加，其实都可以通过 **加法器** 直接相加。计算机做减法，需要引入原码、反码、补码的概念。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "c++"
    }
   },
   "outputs": [],
   "source": [
    "#include <iostream>\n",
    "#include <sstream>\n",
    "#include <string>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 原码\n",
    "\n",
    "- **原码** （true form）是一种计算机中对数字的二进制定点表示方法。原码表示法在数值前面增加了一位符号位（即最高位为符号位）：正数该位为 $0$，负数该位为 $1$（$0$ 有两种表示：$+0$ 和 $-0$），其余位表示数值的大小。\n",
    "- 原码就是符号位加上真值的绝对值，即用第一位表示符号，其余位表示值。\n",
    "\n",
    "比如：如果是 $8$ 位二进制：\n",
    "\n",
    "$$\n",
    "+11_{(10)} = 2^3 + 2^1 + 2^0 = +1011_{(2)}\n",
    "$$\n",
    "\n",
    "这里 `1011` 代表真值，需要转换为 `int8_t`，即表示为 $0000\\ 1011$。\n",
    "\n",
    "因而，`int8_t` 的取值范围为 $[1111\\ 1111, 0111\\ 1111]$，即 $[-127, 127]$。\n",
    "\n",
    "原码的特点：\n",
    "\n",
    "1. 原码表示直观、易懂，与真值转换容易。\n",
    "2. 原码中 $0$ 有两种不同的表示形式，给使用带来了不便。\n",
    "通常 $0$ 的原码用 $+0$ 表示，若在计算过程中出现了 $-0$，则需要用硬件将 $-0$ 变成 $+0$。\n",
    "3. 原码表示加减运算复杂。\n",
    "  利用原码进行两数相加运算时，首先要判别两数符号，若同号则做加法，若异号则做减法。在利用原码进行两数相减运算时，不仅要判别两数符号，使得同号相减，异号相加；还要判别两数绝对值的大小，用绝对值大的数减去绝对值小的数，取绝对值大的数的符号为结果的符号。可见，原码表示不便于实现加减运算。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "## 反码\n",
    "\n",
    "原码最大的问题就在于一个数加上它的相反数不等于 $0$，于是反码的设计思想就是冲着解决这一点，既然一个负数是一个正数的相反数，那干脆用一个正数按位取反来表示负数。\n",
    "\n",
    "反码的表示方法是：\n",
    "\n",
    "1. 正数的反码是其本身；\n",
    "2. 负数的反码是在其原码的基础上，符号位不变，其余按位取反。\n",
    "\n",
    "比如：\n",
    "\n",
    "$$\n",
    "-11_{(10)} = -(2^3 + 2^1 + 2^0) = -1011_{(2)}\n",
    "$$\n",
    "\n",
    "这里 `-1011` 代表真值，需要转换为 `int8_t`，原码表示为 $1000\\ 1011$，反码为 $1111\\ 0100$。\n",
    "\n",
    "反码的特点：\n",
    "\n",
    "1. 在反码表示中，用符号位表示数值的正负，形式与原码表示相同，即 $0$ 为正；$1$ 为负。\n",
    "1. 在反码表示中，数值 $0$ 有两种表示方法。\n",
    "1. 反码的表示范围与原码的表示范围相同。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 补码\n",
    "\n",
    "补码：正数的补码等于它的原码；负数的补码等于反码+1（这只是一种算补码的方式，多数书对于补码就是这句话）。\n",
    "\n",
    "> 其实负数的补码等于反码+1只是补码的求法，而不是补码的定义，很多人以为求补码就要先求反码，其实并不是，那些计算机学家并不会心血来潮的把反码+1就定义为补码，只不过补码正好就等于反码+1而已。\n",
    "\n",
    "补码的特点：\n",
    "\n",
    "1. 在补码表示中，用符号位表示数值的正负，形式与原码的表示相同，即 $0$ 为正，$1$ 为负。但补码的符号可以看做是数值的一部分参加运算。\n",
    "正数的补码表示就是其本身，负数的补码表示的实质是把负数映像到正值区域，因此加上一个负数或减去一个正数可以用加上另一个数（负数或减数对应的补码）来代替。\n",
    "从补码表示的符号看，补码中符号位的值代表了数的正确符号，$0$ 表示正数，$1$ 表示负数；而从映像值来看，符号位的值是映像值的一个数位，因此在补码运算中，符号位可以与数值位一起参加运算。\n",
    "2. 在补码表示中，数值 $0$ 只有一种表示方法。\n",
    "3. 负数补码的表示范围比负数原码的表示范围略宽。纯小数的补码可以表示到-1，纯整数的补码可以表示到 $-2^n$。\n",
    "\n",
    "由于补码表示中的符号位可以与数值位一起参加运算，并且可以将减法转换为加法进行运算，简化了运算过程，因此计算机中均采用补码进行加减运算。\n",
    "\n",
    "```{admonition} 为什么负数的补码的求法是反码+1\n",
    "\n",
    "因为负数的反码加上这个负数的绝对值正好等于1111，在加1，就是10000，也就是四位二进数的模，而负数的补码是它的绝对值的同余数，可以通过模减去负数的绝对值得到它的补码，所以负数的补码就是它的反码+1。\n",
    "```\n",
    "\n",
    "$w$ 位补码的数学定义为：\n",
    "\n",
    "$$\n",
    "- x_{w-1} 2^{w-1} + \\sum_{i=0}^{-2} x_i 2^i\n",
    "$$\n",
    "\n",
    "“二进制上丢弃溢出的位，等价于使用溢出值取模”。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "vscode": {
     "languageId": "c++"
    }
   },
   "outputs": [],
   "source": [
    "int8_t a = 11;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "c++"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "-100 % 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "vscode": {
     "languageId": "c++"
    }
   },
   "outputs": [],
   "source": [
    "std::string bin_uint8(uint8_t num)\n",
    "{\n",
    "  std::ostringstream oss;  // 用于记录信息\n",
    "\tint k;\n",
    "\tuint8_t *p = (uint8_t*)&num;\n",
    "\tfor (int k = 7; k >= 0; k--) //处理 8 个位\n",
    "\t{\n",
    "\t\tif (*p & (1 << k))\n",
    "\t\t\toss << 1;\n",
    "\t\telse\n",
    "\t\t\toss << 0;\n",
    "\t}\n",
    "\treturn oss.str();\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "vscode": {
     "languageId": "c++"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"00001011\""
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bin_uint8(11)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "c++"
    }
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "C++14",
   "language": "C++14",
   "name": "xcpp14"
  },
  "language_info": {
   "codemirror_mode": "text/x-c++src",
   "file_extension": ".cpp",
   "mimetype": "text/x-c++src",
   "name": "C++14",
   "version": "14"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
